import { IDE } from "../../index"
import { findUriInDirs } from "../../util/uri"
import { ContextRetrievalService } from "../context/ContextRetrievalService"
import { GetLspDefinitionsFunction } from "../types"
import { HelperVars } from "../util/HelperVars"
import { openedFilesLruCache } from "../util/openedFilesLruCache"

import {
	AutocompleteClipboardSnippet,
	AutocompleteCodeSnippet,
	AutocompleteDiffSnippet,
	AutocompleteSnippetType,
	AutocompleteStaticSnippet,
} from "./types"

const IDE_SNIPPETS_ENABLED = false // ideSnippets is not used, so it's temporarily disabled

export interface SnippetPayload {
	rootPathSnippets: AutocompleteCodeSnippet[]
	importDefinitionSnippets: AutocompleteCodeSnippet[]
	ideSnippets: AutocompleteCodeSnippet[]
	recentlyEditedRangeSnippets: AutocompleteCodeSnippet[]
	recentlyVisitedRangesSnippets: AutocompleteCodeSnippet[]
	diffSnippets: AutocompleteDiffSnippet[]
	clipboardSnippets: AutocompleteClipboardSnippet[]
	recentlyOpenedFileSnippets: AutocompleteCodeSnippet[]
	staticSnippet: AutocompleteStaticSnippet[]
}

function racePromise<T>(promise: Promise<T[]>, timeout = 100): Promise<T[]> {
	const timeoutPromise = new Promise<T[]>((resolve) => {
		setTimeout(() => resolve([]), timeout)
	})

	return Promise.race([promise, timeoutPromise])
}

// Some IDEs might have special ways of finding snippets (e.g. JetBrains and VS Code have different "LSP-equivalent" systems,
// or they might separately track recently edited ranges)
async function getIdeSnippets(
	helper: HelperVars,
	ide: IDE,
	getDefinitionsFromLsp: GetLspDefinitionsFunction,
): Promise<AutocompleteCodeSnippet[]> {
	const ideSnippets = await getDefinitionsFromLsp(
		helper.input.filepath,
		helper.fullPrefix + helper.fullSuffix,
		helper.fullPrefix.length,
		ide,
		helper.lang,
	)

	if (helper.options.onlyMyCode) {
		const workspaceDirs = await ide.getWorkspaceDirs()

		return ideSnippets.filter((snippet) =>
			workspaceDirs.some((dir) => !!findUriInDirs(snippet.filepath, [dir]).foundInDir),
		)
	}

	return ideSnippets
}

function getSnippetsFromRecentlyEditedRanges(helper: HelperVars): AutocompleteCodeSnippet[] {
	if (helper.options.useRecentlyEdited === false) {
		return []
	}

	return helper.input.recentlyEditedRanges.map((range) => {
		return {
			filepath: range.filepath,
			content: range.lines.join("\n"),
			type: AutocompleteSnippetType.Code,
		}
	})
}

const getClipboardSnippets = async (ide: IDE): Promise<AutocompleteClipboardSnippet[]> => {
	const content = await ide.getClipboardContent()

	return [content].map((item) => {
		return {
			content: item.text,
			copiedAt: item.copiedAt,
			type: AutocompleteSnippetType.Clipboard,
		}
	})
}

const getSnippetsFromRecentlyOpenedFiles = async (helper: HelperVars, ide: IDE): Promise<AutocompleteCodeSnippet[]> => {
	if (helper.options.useRecentlyOpened === false) {
		return []
	}

	try {
		const currentFileUri = `${helper.filepath}`

		// Get all file URIs excluding the current file
		const fileUrisToRead = [...openedFilesLruCache.entriesDescending()]
			.filter(([fileUri, _]) => fileUri !== currentFileUri)
			.map(([fileUri, _]) => fileUri)

		// Create an array of promises that each read a file with timeout
		const fileReadPromises = fileUrisToRead.map((fileUri) => {
			// Create a promise that resolves to a snippet or null
			const readPromise = new Promise<AutocompleteCodeSnippet | null>((resolve) => {
				ide.readFile(fileUri)
					.then((fileContent) => {
						if (!fileContent || fileContent.trim() === "") {
							resolve(null)
							return
						}

						resolve({
							filepath: fileUri,
							content: fileContent,
							type: AutocompleteSnippetType.Code,
						})
					})
					.catch((e) => {
						console.error(`Failed to read file ${fileUri}:`, e)
						resolve(null)
					})
			})
			// Cut off at 80ms via racing promises
			return Promise.race([readPromise, new Promise<null>((resolve) => setTimeout(() => resolve(null), 80))])
		})

		// Execute all file reads in parallel
		const results = await Promise.all(fileReadPromises)

		// Filter out null results
		return results.filter(Boolean) as AutocompleteCodeSnippet[]
	} catch (e) {
		console.error("Error processing opened files cache:", e)
		return []
	}
}

export const getAllSnippets = async ({
	helper,
	ide,
	getDefinitionsFromLsp,
	contextRetrievalService,
}: {
	helper: HelperVars
	ide: IDE
	getDefinitionsFromLsp: GetLspDefinitionsFunction
	contextRetrievalService: ContextRetrievalService
}): Promise<SnippetPayload> => {
	const recentlyEditedRangeSnippets = getSnippetsFromRecentlyEditedRanges(helper)

	const [
		rootPathSnippets,
		importDefinitionSnippets,
		ideSnippets,
		diffSnippets,
		clipboardSnippets,
		recentlyOpenedFileSnippets,
		staticSnippet,
	] = await Promise.all([
		racePromise(contextRetrievalService.getRootPathSnippets(helper)),
		racePromise(contextRetrievalService.getSnippetsFromImportDefinitions(helper)),
		IDE_SNIPPETS_ENABLED ? racePromise(getIdeSnippets(helper, ide, getDefinitionsFromLsp)) : [],
		[], // racePromise(getDiffSnippets(ide)) // temporarily disabled, see https://github.com/continuedev/continue/pull/5882,
		racePromise(getClipboardSnippets(ide)),
		racePromise(getSnippetsFromRecentlyOpenedFiles(helper, ide)), // giving this one a little more time to complete
		helper.options.experimental_enableStaticContextualization
			? racePromise(contextRetrievalService.getStaticContextSnippets(helper))
			: [],
	])

	return {
		rootPathSnippets,
		importDefinitionSnippets,
		ideSnippets,
		recentlyEditedRangeSnippets,
		diffSnippets,
		clipboardSnippets,
		recentlyVisitedRangesSnippets: helper.input.recentlyVisitedRanges,
		recentlyOpenedFileSnippets,
		staticSnippet,
	}
}

export const getAllSnippetsWithoutRace = async ({
	helper,
	ide,
	getDefinitionsFromLsp,
	contextRetrievalService,
}: {
	helper: HelperVars
	ide: IDE
	getDefinitionsFromLsp: GetLspDefinitionsFunction
	contextRetrievalService: ContextRetrievalService
}): Promise<SnippetPayload> => {
	const recentlyEditedRangeSnippets = getSnippetsFromRecentlyEditedRanges(helper)

	const [
		rootPathSnippets,
		importDefinitionSnippets,
		ideSnippets,
		diffSnippets,
		clipboardSnippets,
		recentlyOpenedFileSnippets,
		staticSnippet,
	] = await Promise.all([
		contextRetrievalService.getRootPathSnippets(helper),
		contextRetrievalService.getSnippetsFromImportDefinitions(helper),
		IDE_SNIPPETS_ENABLED ? getIdeSnippets(helper, ide, getDefinitionsFromLsp) : [],
		[], // racePromise(getDiffSnippets(ide)) // temporarily disabled, see https://github.com/continuedev/continue/pull/5882,
		getClipboardSnippets(ide),
		getSnippetsFromRecentlyOpenedFiles(helper, ide),
		helper.options.experimental_enableStaticContextualization
			? contextRetrievalService.getStaticContextSnippets(helper)
			: [],
	])

	return {
		rootPathSnippets,
		importDefinitionSnippets,
		ideSnippets,
		recentlyEditedRangeSnippets,
		diffSnippets,
		clipboardSnippets,
		recentlyVisitedRangesSnippets: helper.input.recentlyVisitedRanges,
		recentlyOpenedFileSnippets,
		staticSnippet,
	}
}
